package com.tianji.learning.service.impl;

import com.tianji.api.client.course.CourseClient;
import com.tianji.api.dto.course.CourseFullInfoDTO;
import com.tianji.api.dto.leanring.LearningLessonDTO;
import com.tianji.api.dto.leanring.LearningRecordDTO;
import com.tianji.common.autoconfigure.mq.RabbitMqHelper;
import com.tianji.common.constants.MqConstants;
import com.tianji.common.exceptions.BizIllegalException;
import com.tianji.common.exceptions.DbException;
import com.tianji.common.utils.BeanUtils;
import com.tianji.common.utils.UserContext;
import com.tianji.learning.entity.dto.LearningRecordFormDTO;
import com.tianji.learning.entity.mq.SignInMessage;
import com.tianji.learning.entity.po.LearningLesson;
import com.tianji.learning.entity.po.LearningRecord;
import com.tianji.learning.enums.LessonStatus;
import com.tianji.learning.enums.SectionType;
import com.tianji.learning.mapper.LearningRecordMapper;
import com.tianji.learning.service.ILearningLessonService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.tianji.learning.service.ILearningRecordService;
import com.tianji.learning.utils.LearningRecordDelayTaskHandler;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.util.ObjectUtils;

import java.util.List;

/**
 * <p>
 * 学习记录表 服务实现类
 * </p>
 *
 * @author lemon
 */
@RequiredArgsConstructor
@Slf4j
@Service
public class LearningRecordServiceImpl extends ServiceImpl<LearningRecordMapper, LearningRecord> implements ILearningRecordService {

    // 如果出现了循环引用就使用mapper
    private final ILearningLessonService learningLessonService;

    private final CourseClient courseClient;

    private final LearningRecordDelayTaskHandler learningRecordDelayTaskHandler;

    private final RabbitMqHelper mqHelper;

    /**
     * 查询当前用户指定课程的学习进度
     * 一般feign传递是用dto
     *
     * @param courseId
     * @return
     */
    @Override
    public LearningLessonDTO queryLearningRecordByCourse(Long courseId) {
        // 获取当前用户
        Long userId = UserContext.getUser();

        // 根据courseId和userId查询课表
        LearningLesson lesson = learningLessonService.lambdaQuery().eq(LearningLesson::getUserId, userId)
                .eq(LearningLesson::getCourseId, courseId)
                .one();

        // 判断是否存在课表
        if (lesson == null) {
            // 业务异常：课表不存在
            throw new BizIllegalException("该课程未加入课表");
        }

        // 得到课表id
        Long lessonId = lesson.getId();
        List<LearningRecord> list = this.lambdaQuery().eq(LearningRecord::getLessonId, lessonId)
                .eq(LearningRecord::getUserId, userId)
                .list();

        // 得到全部的学习记录
        // 参数封装
        LearningLessonDTO dto = new LearningLessonDTO();
        dto.setId(lessonId);
        // 最新学的章节id
        dto.setLatestSectionId(lesson.getLatestSectionId());

        List<LearningRecordDTO> learningRecordDTOS = BeanUtils.copyList(list, LearningRecordDTO.class);
        dto.setRecords(learningRecordDTOS);

        return dto;


    }

    /**
     * 提交学习记录
     *
     * @param formDTO
     */
    @Override
    public void addLearningRecord(LearningRecordFormDTO formDTO) {
        // 1.获取到当前用户
        Long userId = UserContext.getUser();

        // 看是什么类型
        SectionType sectionType = formDTO.getSectionType();

        // 表示本小节是否已经学完
        boolean isFinished = false;
        //
        if (sectionType.equals(SectionType.VIDEO)) {
            // 如果是视频的话
            //isFinished = handleVideoRecord(userId, formDTO);
            isFinished = handleVideoRecordWithCacheAndDelayQueue(userId, formDTO);
        } else {
            // 就是考试的话
            isFinished = handleExamRecord(userId, formDTO);
        }

        // 处理课表数据
        handleLessonData(formDTO, isFinished);

        // 2.判断是考试和学习
        // 2.1 考试一旦考试完成就让这一小节完成也就是record表
        // 然后还要更新lesson表的latestSectionId 以及更新lesson表的isFinished

        // 2.2 如果是播放视频的话

    }

    // 处理课表数据
    private void handleLessonData(LearningRecordFormDTO formDTO, boolean isFinished) {
        // 更新最新学习的章节id和时间
        // 根据课程id和用户id查询课表
        Long lessonId = formDTO.getLessonId();
        // 根据课表id查询课表
        // 得到课表
        LearningLesson lesson = learningLessonService.getById(lessonId);
        // 校验
        if (ObjectUtils.isEmpty(lesson)) {
            throw new BizIllegalException("课表不存在");
        }
        if (LessonStatus.EXPIRED.equals(lesson.getStatus())) {
            // 课程已经过期了
            throw new BizIllegalException("课程已经过期了");
        }
        // 也就是没有学完
        if (!lesson.getStatus().equals(LessonStatus.FINISHED)) {
            // 设置课程的状态为学习中
            lesson.setStatus(LessonStatus.LEARNING);
            if (isFinished) {
                // 远程调用course服务查询课程的所有个数
                // 根据课程id查询课程的所有章节数
                CourseFullInfoDTO courseInfo = courseClient.getCourseInfoById(lesson.getCourseId(), false, false);
                // 得到课程的章节数
                Integer sectionCount = courseInfo.getSectionNum();
                // 小节数量+1
                Integer finishedSectionCount = lesson.getLearnedSections() + 1;
                // 完成的小节数量
                lesson.setLearnedSections(finishedSectionCount);
                // 如果写完了
                if (finishedSectionCount.equals(sectionCount)) {
                    // 表示课程已经学完了
                    lesson.setStatus(LessonStatus.FINISHED);
                }
            }

        }
        // 更新最新学习的章节id和时间
        lesson.setLatestLearnTime(formDTO.getCommitTime());
        lesson.setLatestSectionId(formDTO.getSectionId());

        // 更新课表
        boolean update = learningLessonService.updateById(lesson);
        if (!update) {
            throw new DbException("更新课表失败");
        }

    }

    // 处理视频播放的记录
    private boolean handleVideoRecord(Long userId, LearningRecordFormDTO formDTO) {
        // 先判断学习记录是否存在
        // 根据小节id，lessonId和用户id查询学习记录
        LearningRecord one = this.lambdaQuery().eq(LearningRecord::getLessonId, formDTO.getLessonId())
                .eq(LearningRecord::getUserId, userId)
                //
                .eq(LearningRecord::getSectionId, formDTO.getSectionId())
                .one();
        if (one == null) {
            // 学习记录不存在就添加
            LearningRecord learningRecord = BeanUtils.copyBean(formDTO, LearningRecord.class);
            learningRecord.setUserId(userId);
            // 肯定是没有学习完成的
            learningRecord.setFinished(false);

            boolean save = this.save(learningRecord);
            if (!save) {
                throw new DbException("新增视频学习记录失败");
            }
            // 因为是第一次学习所以肯定是没有学习完成的
            return false;
        }

        Boolean finished = one.getFinished();
        if (finished) {
            // 表示已经学完了
            // 只需要修改时间 你随便改 反正你都是学过的
            this.lambdaUpdate().set(LearningRecord::getMoment, formDTO.getMoment())
                    // 完成时间也不用修改了
                    .eq(LearningRecord::getId, one.getId())
                    .update();
            // 不是第一次学完
            return false;
        }
        // 表示当时没有学完
        // getDuration表示视频的时长
        // 也就是说你是未完成并且你的时长大于等于视频的一半就可以更新表示你看完了
        boolean isFinished = formDTO.getMoment() * 2 >= formDTO.getDuration();
        boolean update; // 表示是否更新成功
        if (isFinished) {
            // 表示看到一半了
            update = this.lambdaUpdate().set(LearningRecord::getFinished, true)
                    .set(LearningRecord::getFinishTime, formDTO.getCommitTime())
                    .set(LearningRecord::getMoment, formDTO.getMoment())
                    .eq(LearningRecord::getId, one.getId())
                    .update();

            // TODO 添加积分  添加到mq
            SignInMessage signInMessage = new SignInMessage();
            signInMessage.setUserId(userId);
            signInMessage.setPoints(10);
            mqHelper.send(
                    MqConstants.Exchange.LEARNING_EXCHANGE,
                    MqConstants.Key.LEARN_SECTION,
                    signInMessage
            );


        } else {
            // 表示还没有看到一半
            // 如果没有学完就更新学习记录
            update = this.lambdaUpdate().set(LearningRecord::getMoment, formDTO.getMoment())
                    .eq(LearningRecord::getId, one.getId())
                    .update();
        }
        if (!update) {
            throw new DbException("更新视频学习记录失败");
        }

        // 返回是否学完
        return isFinished;

    }

    // 处理视频播放的记录--缓存
    private boolean handleVideoRecordWithCacheAndDelayQueue(Long userId, LearningRecordFormDTO formDTO) {
        // 先判断学习记录是否存在
        LearningRecord learningRecord = new LearningRecord();
        // 先查询缓存
        learningRecord = queryRecordWithCache(formDTO.getLessonId(), formDTO.getSectionId());
        // 缓存不存在查询db并且添加缓存
        if (learningRecord == null) {
            learningRecord = queryRecordWithDb(userId, formDTO);
        }

        // 如果数据库还是查询不到就添加一个新的
        // 根据小节id，lessonId和用户id查询学习记录

        if (learningRecord == null) {
            // 学习记录不存在就添加
            LearningRecord learningRecordNew = BeanUtils.copyBean(formDTO, LearningRecord.class);
            learningRecordNew.setUserId(userId);
            // learningRecordNew
            learningRecordNew.setFinished(false);

            boolean save = this.save(learningRecordNew);
            if (!save) {
                throw new DbException("新增视频学习记录失败");
            }
            // 你也可以加入缓存
            // 放入缓存
//          learningRecordDelayTaskHandler.writeRecordCache(learningRecordNew);

            // 因为是第一次学习所以肯定是没有学习完成的
            return false;
        }

        // 判断是否学完过
        Boolean finished = learningRecord.getFinished();
        if (finished) {
            // 表示已经学完了
            // setMoment表示更新时间
            learningRecord.setMoment(formDTO.getMoment());
            // 如果学完就更新一下缓存里面的moment
            learningRecordDelayTaskHandler.writeRecordCache(learningRecord);

            // 并且不是第一次学完
            return false;
        }
        // 表示当时没有学完
        // getDuration表示视频的时长
        // 也就是说你是未完成并且你的时长大于等于视频的一半就可以更新表示你看完了
        boolean isFinished = formDTO.getMoment() * 2 >= formDTO.getDuration();
        boolean update; // 表示是否更新成功
        if (isFinished) {
            // 去更新学习记录
            // 表示看到一半了
            update = this.lambdaUpdate().set(LearningRecord::getFinished, true)
                    .set(LearningRecord::getFinishTime, formDTO.getCommitTime())
                    .set(LearningRecord::getMoment, formDTO.getMoment())
                    .eq(LearningRecord::getId, learningRecord.getId())
                    .update();
            if (!update) {
                throw new DbException("更新视频学习记录失败");
            }


            // 清理缓存
            // 注意不能从 从缓存里面查询到learningRecord中获取lessonId和sectionId
            // 因为存入redis中的是没有lessonId和sectionId的
            // learningRecordDelayTaskHandler.cleanRecordCache(learningRecord.getLessonId(), learningRecord.getSectionId());
            // 因为一直查询是的缓存中的lessonId和sectionId都是null
            learningRecordDelayTaskHandler.cleanRecordCache(formDTO.getLessonId(), formDTO.getSectionId());

        } else {
            // 表示没有学习完只是更新学习记录，然后提交到缓存
            LearningRecord record = new LearningRecord();
            record.setId(learningRecord.getId());
            // 表单提交的时间
            record.setMoment(formDTO.getMoment());
            // 从缓存里面查询的是没有lessonId的
            record.setLessonId(formDTO.getLessonId());
            // 表单提交的小节id
            record.setSectionId(formDTO.getSectionId());
            record.setFinished(learningRecord.getFinished());
            learningRecordDelayTaskHandler.addLearningRecordToDelayQueue(record);
        }

        // 返回是否学完
        return isFinished;

    }

    /**
     * 从缓存中查询学习记录
     *
     * @param lessonId
     * @param sectionId
     * @return
     */
    private LearningRecord queryRecordWithCache(Long lessonId, Long sectionId) {
        return learningRecordDelayTaskHandler.readRecordCache(lessonId, sectionId);
    }

    /**
     * 从数据库中查询学习记录
     *
     * @param userId
     * @param formDTO
     * @return
     */
    private LearningRecord queryRecordWithDb(Long userId, LearningRecordFormDTO formDTO) {
        LearningRecord learningRecord = this.lambdaQuery().eq(LearningRecord::getLessonId, formDTO.getLessonId())
                .eq(LearningRecord::getUserId, userId)
                .eq(LearningRecord::getSectionId, formDTO.getSectionId())
                .one();

        // 如果数据库还是查询不到就添加一个新的
        if (learningRecord == null) {
            return null;
            // 在后面我们会添加上去的
        }

        // 写入缓存
        learningRecordDelayTaskHandler.writeRecordCache(learningRecord);
        return learningRecord;
    }

    // 处理考试的记录
    private boolean handleExamRecord(Long userId, LearningRecordFormDTO formDTO) {

        // 将dto转为po
        LearningRecord record = BeanUtils.copyProperties(formDTO, LearningRecord.class);
        record.setUserId(userId);
        // 将finished设置为true
        record.setFinished(true);
        record.setFinishTime(formDTO.getCommitTime());

        // 新增一条学习记录 learning_record
        boolean save = this.save(record);
        if (!save) {
            throw new DbException("新增考试记录失败");
        }

        // 下面就判断是否学完了

        return true;
    }

}
